﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace GIVisualEditor
{
    public partial class StateConnector : UserControl
    {
        private DirectedGraph graph = null;
        private Edge edge = null;

        public Edge AssociatedEdge
        {
            get { return edge; }
        }


        private BindCorner bindCorner1 = BindCorner.Right, bindCorner2 = BindCorner.Left;

        /// <summary>
        /// 0 to 3 indicating one of the 4-corners of this control
        /// 
        ///           0 ---------------------- 1
        ///           |                        |
        ///           |                        |
        ///           |                        |
        ///           3 ---------------------- 2
        /// 
        /// </summary>
        private int startPointCornerIndex = 0, endPointCornerIndex = 0;


        public StateConnector(DirectedGraph g, Edge e)
        {
            this.graph = g; this.edge = e;
            InitializeComponent();
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            SetStyle(ControlStyles.Opaque, true);
            this.SetStyle(ControlStyles.DoubleBuffer, true);
            this.BackColor = Color.Transparent;
            this.Paint += new PaintEventHandler(StateConnector_Paint);
            
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x20;
                return cp;
            }
        }

        private bool IsInRange(int start, int end, int x)
        {
            return (start < end) ? (start <= x && x <= end ) : (end <= x && x <= start );
        }

        /// <summary>
        /// Finds the intersection of line segment joining P1 and P2 with Rectangle R1. Returns true if an intersection was found, P contains the result
        /// </summary>
        /// <param name="P1"></param>
        /// <param name="P2"></param>
        /// <param name="R"></param>
        /// <param name="P"></param>
        /// <returns></returns>
        private bool FindIntersection(Point P1, Point P2, Rectangle R, ref Point P)
        {
            bool result = false;
            if (P1.X == P2.X)
            {
                // Veritical line
                if (P1.X == R.Left)
                {
                    P = new Point(P1.X, R.Top + R.Height / 2);
                    result = true;
                } else
                    if (P1.X == R.Right)
                    {
                        P = new Point(P1.X, R.Top + R.Height / 2);
                        result = true;
                    }
                    if( IsInRange(P1.Y, P2.Y, R.Top)  && IsInRange(R.Left, R.Right, P1.X) )
                    {
                        P = new Point(P1.X, R.Top);
                        result = true;

                    } else
                        if (IsInRange(P1.Y, P2.Y, R.Top + R.Height) && IsInRange(R.Left, R.Right, P1.X))
                        {
                            P = new Point(P1.X, R.Top + R.Height);
                            result = true;
                        }

            }
            else
            {   
                // Slant line
                double  leftVertY1 = GetPointSideIndicator( P1, P2, R.Left, R.Top),
                        leftVertY2 = GetPointSideIndicator(P1, P2, R.Left, R.Top + R.Height),

                        rightVertY1 = GetPointSideIndicator(P1, P2, R.Left + R.Width, R.Top),
                        rightVertY2 = GetPointSideIndicator(P1, P2, R.Left + R.Width, R.Top + R.Height),

                        topHorizY1 = GetPointSideIndicator(P1,P2, R.Left, R.Top),
                        topHorizY2 = GetPointSideIndicator(P1, P2,R.Left + R.Width, R.Top),

                        bottomHorizY1 = GetPointSideIndicator(P1, P2, R.Left, R.Top + R.Height) ,
                        bottomHorizY2 = GetPointSideIndicator(P1, P2, R.Left + R.Width, R.Top + R.Height);
                // Line intersects if their SideIndicator changes sign => 
                bool leftVertIntersects = (leftVertY1 >= 0 && leftVertY2 <= 0) || (leftVertY1 <= 0 && leftVertY2 >= 0 ),

                     rightVertIntersects = (rightVertY1 >= 0 && rightVertY2 <= 0) || (rightVertY1 <= 0 && rightVertY2 >= 0),

                     topHorizIntersects = (topHorizY1 >= 0 && topHorizY2 <= 0) || (topHorizY1 <= 0 && topHorizY2 >= 0),

                     bottomHorizIntersects = (bottomHorizY1 >= 0 && bottomHorizY2 <= 0) || (bottomHorizY1 <= 0 && bottomHorizY2 >= 0);

                double m = ((double)(P2.Y - P1.Y)) / (double)(P2.X - P1.X), x =0d, y =0d, deltaY=0, deltaX=0;

                if (!result && leftVertIntersects)
                {
                    x = R.Left;
                    deltaY = m * (x - P1.X);
                    // slope and deltaY should have same sign, otherwise the intersection is outside the segment
                    //if ((deltaY >= 0 && m >= 0) || (deltaY < 0 && m < 0))
                    if(IsInRange(P1.X, P2.X, R.Left))
                    {
                        y = P1.Y + deltaY;
                        P = new Point((int)Math.Round(x, MidpointRounding.AwayFromZero), (int)Math.Round(y, MidpointRounding.AwayFromZero));
                        result = true;
                    }
                }

                if (!result && rightVertIntersects)
                {
                    x = R.Left + R.Width;
                    deltaY = m * (x - P1.X);
                    // slope and deltaY should have same sign, otherwise the intersection is outside the segment
                    //if ((deltaY >= 0 && m >= 0) || (deltaY < 0 && m < 0))
                    if (IsInRange(P1.X, P2.X, R.Right))
                    {
                        y = P1.Y + deltaY;
                        P = new Point((int)Math.Round(x, MidpointRounding.AwayFromZero), (int)Math.Round(y, MidpointRounding.AwayFromZero));
                        result = true;
                    }

                }

                if (!result && topHorizIntersects)
                {
                    y = R.Top;
                    deltaX = (y - P1.Y) / m;

                    if (IsInRange(P1.Y, P2.Y, R.Top))
                    {
                        x = P1.X + deltaX;
                        P = new Point((int)Math.Round(x, MidpointRounding.AwayFromZero), (int)Math.Round(y, MidpointRounding.AwayFromZero));
                        result = true;
                    }

                }

                if (!result && bottomHorizIntersects)
                {
                    y = R.Top + R.Height;
                    deltaX = (y - P1.Y) / m;

                    // slope and deltaY should have same sign, otherwise the intersection is outside the segment
                    if (IsInRange(P1.Y, P2.Y, R.Bottom))
                    {
                        x = P1.X + deltaX;
                        P = new Point((int)Math.Round(x, MidpointRounding.AwayFromZero), (int)Math.Round(y, MidpointRounding.AwayFromZero));
                        result = true;
                    }
                }
            }

            return result;
        }

        

        private double GetPointSideIndicator(Point P1, Point P2, int x, int y)
        {
            double m = ((double)P2.Y - P1.Y) / ((double)P2.X - P1.X);
            // The line is define by equuation : y = m * (x - P1.x) + P1.y
            double r = y - (m * (x - P1.X) + P1.Y);

            return r;
        }

        private int Min(int x, int y)
        {
            return (x < y) ? x : y;
        }

        private bool SetControlDimension()
        {
            Point P1 = new Point(this.edge.FromState.View.Left + this.edge.FromState.View.Width / 2, this.edge.FromState.View.Top + this.edge.FromState.View.Height / 2),
                  P2 = new Point(this.edge.ToState.View.Left + this.edge.ToState.View.Width / 2, this.edge.ToState.View.Top + this.edge.ToState.View.Height / 2);

            Point connectorPoint1 = new Point(0, 0), connectorPoint2 = new Point(0, 0);
            Rectangle R1 = new Rectangle(this.edge.FromState.View.Left, this.edge.FromState.View.Top, this.edge.FromState.View.Width, this.edge.FromState.View.Height),
                        R2 = new Rectangle(this.edge.ToState.View.Left, this.edge.ToState.View.Top, this.edge.ToState.View.Width, this.edge.ToState.View.Height);

            bool b1 = FindIntersection(P1, P2, R1, ref connectorPoint1),
                 b2 = FindIntersection(P2, P1, R2, ref connectorPoint2);

            if (b1 && b2)
            {
                int w = connectorPoint2.X - connectorPoint1.X, 
                    h = connectorPoint2.Y - connectorPoint1.Y;

                this.Width = (w > 0) ? w : ((w <0 ) ? -w : 6);
                this.Height = (h > 0) ? h : ((h < 0) ? -h : 6);
                this.Left = Min(connectorPoint1.X, connectorPoint2.X);
                this.Top = Min(connectorPoint1.Y, connectorPoint2.Y);


                if (w >= 0)
                {
                    if (h >= 0)
                    {
                        startPointCornerIndex = 2; endPointCornerIndex = 0;
                        bindCorner1 = BindCorner.Left; bindCorner2 = BindCorner.Right;
                    }
                    else
                    {
                        startPointCornerIndex = 1; endPointCornerIndex = 3;
                        bindCorner1 = BindCorner.Left; bindCorner2 = BindCorner.Right;
                    }
                }
                else
                {
                    if (h >= 0)
                    {
                        startPointCornerIndex = 3; endPointCornerIndex = 1;
                        bindCorner1 = BindCorner.Right; bindCorner2 = BindCorner.Left;
                    }
                    else
                    {
                        startPointCornerIndex = 0; endPointCornerIndex = 2;
                        bindCorner1 = BindCorner.Right; bindCorner2 = BindCorner.Left;
                    }
                }

            }

            return b1 && b2;
        }



        
        private Point EvaluateBindPosition(bool firstState)
        {
            int x = 0, y = 0;
            Node N = (firstState) ? edge.FromState : edge.ToState;
            BindCorner bindCorner = (firstState) ? bindCorner1 : bindCorner2;

            switch (bindCorner)
            {
                case BindCorner.Left: x = N.View.Left; y = N.View.Top + (N.View.Height / 2);
                    break;
                case BindCorner.Top: x = N.View.Left + (N.View.Width / 2); y = N.View.Top;
                    break;
                case BindCorner.Right: x = N.View.Left + N.View.Width; y = N.View.Top + (N.View.Height / 2);
                    break;
                case BindCorner.Bottom: x = N.View.Left + (N.View.Width / 2); y = N.View.Top + N.View.Height;
                    break;
            }

            return new Point(x, y);
        }

        private Point GetCornerPosition(int cornerIndex)
        {
            switch (cornerIndex)
            {
                case 0: return new Point(0, 0);
                    break;

                case 1: return new Point(this.Width, 0);
                    break;

                case 2: return new Point(this.Width, this.Height);
                    break;

                case 3: return new Point(0, this.Height);
                    break;

            }

            return Point.Empty;
        }

        void StateConnector_Paint(object sender, PaintEventArgs e)
        {
            if (graph != null && edge != null && e.Graphics != null)
            {
                SetControlDimension();
                Point p1 = GetCornerPosition(startPointCornerIndex), p2 = GetCornerPosition(endPointCornerIndex);
                Pen pen = new Pen(this.ForeColor, 2.0f);
                System.Drawing.Drawing2D.AdjustableArrowCap myArrow = new System.Drawing.Drawing2D.AdjustableArrowCap(5, 8, true);

                pen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
                pen.CustomStartCap = myArrow;

                e.Graphics.DrawLine(pen, p1, p2);
               
            }
        }

    }

    public enum BindCorner
    {
        Left =0,
        Top,
        Right,
        Bottom
    }
}
